﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Text;
using System.Threading.Tasks;
using StyleMVVM.Data.Activation;
using StyleMVVM.Messenger;

namespace StyleMVVM.DependencyInjection.Impl
{
	/// <summary>
	/// The CompiledActivationStrategy is the heart of the DependencyInjectionContainer
	/// Using Linq Expressions it compiles a method that will construct an object
	/// using the provided parameters. 
	/// Then import all needed properties and Register for all messages.
	/// </summary>
	/// <typeparam name="T"></typeparam>
	public sealed class AttributedExportStrategy : IExportStrategy
	{
		private IActivationStrategy activationStrategy;
		private bool initalized = false;
		private object syncObject = new object();
		private volatile List<IExportCondition> conditions;
		private volatile List<IExportPipelineHandler> pipelineHandlers;
		private volatile IEnumerable<KeyValuePair<string, object>> metadata;

		public AttributedExportStrategy(Type activationType)
		{
			ActivationType = activationType;

			ExportNames = new[] { activationType.FullName };

			Environment = ExportEnvironment.Any;
		}

		public AttributedExportStrategy(IEnumerable<string> exportNames, Type activationType)
		{
			ActivationType = activationType;

			ExportNames = exportNames;

			Environment = ExportEnvironment.Any;
		}

		public IDependencyInjectionContainer Container { get; set; }

		public IActivationService ActivationService { get; set; }

		public IEnumerable<string> ExportNames { get; set; }

		public IEnumerable<Type> ExportedTypes { get; set; }

		public Type ActivationType { get; set; }

		public bool ExternallyOwned { get; set; }

		public BeforeDisposalCleanupDelegate DisposalCleanupDelegate { get; set; }

		public ILifeCycleContainer LifeCycleContainer { get; set; }

		public bool Suspendable { get; set; }

		public ExportEnvironment Environment { get; set; }

		public int Priority { get; set; }

		public bool IsCoreExport { get; set; }

		public int CoreExportStartOrder { get; set; }

		public IEnumerable<ImportPropertyInfo> ImportProperties { get; set; }

		public IList<object> PipelineHandlers { get; set; }

		public void Initialize()
		{
			if (!initalized)
			{
				activationStrategy =
					ActivationService.CreateActivationStrategy(
						new ActivationTypeInfo
							{
								ActivationType = ActivationType,
								DiscoverAttributes = true,
								EnrichWithDelegates = EnrichWithDelegates
							});

				initalized = true;
			}
		}

		public void AddPiplineHandler(IExportPipelineHandler pipelineHandler)
		{
			lock (syncObject)
			{
				List<IExportPipelineHandler> tempPipelines = new List<IExportPipelineHandler>();

				if (pipelineHandlers != null)
				{
					tempPipelines.AddRange(pipelineHandlers);
				}

				tempPipelines.Add(pipelineHandler);

				pipelineHandlers = tempPipelines;
			}
		}

		public void RemovePipelineHandler(IExportPipelineHandler pipelineHandler)
		{
			lock (syncObject)
			{
				if (pipelineHandlers != null)
				{
					List<IExportPipelineHandler> tempHandlers = new List<IExportPipelineHandler>(pipelineHandlers);

					tempHandlers.Remove(pipelineHandler);

					if (tempHandlers.Count == 0)
					{
						pipelineHandlers = null;
					}
					else
					{
						pipelineHandlers = tempHandlers;
					}
				}
			}
		}

		public bool MeetsConditions(IDependencyInjectionContainer container, IInjectionContext injectionContext)
		{
			List<IExportCondition> localConditions = conditions;

			if (localConditions == null)
			{
				return true;
			}

			foreach (IExportCondition exportCondition in localConditions)
			{
				if (!exportCondition.ConditionMeet(container, injectionContext, this))
				{
					return false;
				}
			}

			return true;
		}

		public void AddCondition(IExportCondition condition)
		{
			lock (syncObject)
			{
				List<IExportCondition> localConditions = new List<IExportCondition> { condition };

				if (conditions != null)
				{
					localConditions.AddRange(conditions);
				}

				conditions = localConditions;
			}
		}

		public void RemoveCondition(IExportCondition condition)
		{
			lock (syncObject)
			{
				if (conditions != null)
				{
					List<IExportCondition> localConditions = new List<IExportCondition>(conditions);

					localConditions.Remove(condition);

					if (localConditions.Count != 0)
					{
						conditions = localConditions;
					}
					else
					{
						conditions = null;
					}
				}
			}
		}

		public IEnumerable<IExportCondition> Conditions()
		{
			List<IExportCondition> localConditions = conditions;

			if (localConditions == null)
			{
				return new IExportCondition[] { };
			}

			return localConditions.ToArray();
		}

		public IEnumerable<KeyValuePair<string, object>> Metadata
		{
			get
			{
				if (metadata != null)
				{
					return metadata;
				}

				return new KeyValuePair<string, object>[0];
			}
			set { metadata = value; }
		}

		public IEnumerable<EnrichWithDelegate> EnrichWithDelegates { get; set; }

		public object Activate(IInjectionContext injectionContext, [ReadOnlyArray] object[] constructorParams)
		{
			if (!initalized)
			{
				Initialize();
			}

			if (LifeCycleContainer != null)
			{
				return LifeCycleContainer.RetrieveObject(this, InternalActivate, Container, injectionContext, constructorParams);
			}

			return InternalActivate(injectionContext, constructorParams);
		}

		private object InternalActivate(IInjectionContext injectionContext, object[] constructorParams)
		{
			object returnValue = activationStrategy.Activate(injectionContext, constructorParams);

			if (pipelineHandlers != null && returnValue != null)
			{
				foreach (IExportPipelineHandler exportPipelineHandler in pipelineHandlers)
				{
					exportPipelineHandler.ProcessExport(returnValue, this);
				}
			}

			if (returnValue is IDisposable &&
				 LifeCycleContainer == null &&
				 injectionContext.DisposalScope != null &&
				 !ExternallyOwned)
			{
				injectionContext.DisposalScope.AddObject(returnValue as IDisposable, DisposalCleanupDelegate);
			}

			return returnValue;
		}
	}
}